home *** CD-ROM | disk | FTP | other *** search
/ Macwelt 1 / Macwelt DVD 1.toast / Web-Publishing / HTML-Editoren / Alpha ƒ / Tcl / Modes / HTML and CSS Modes / htmlCustom.tcl < prev    next >
Encoding:
Text File  |  2001-01-14  |  30.4 KB  |  829 lines

  1. ## -*-Tcl-*-
  2.  # ###################################################################
  3.  #  HTML mode - tools for editing HTML documents
  4.  # 
  5.  #  FILE: "htmlCustom.tcl"
  6.  #                                    created: 96-06-29 21.36.50 
  7.  #                                last update: 01-01-14 12.32.47 
  8.  #  Author: Johan Linde
  9.  #  E-mail: <alpha_www_tools@go.to>
  10.  #     www: <http://go.to/alpha_www_tools>
  11.  #  
  12.  # Version: 3.0
  13.  # 
  14.  # Copyright 1996-2001 by Johan Linde
  15.  #  
  16.  # This program is free software; you can redistribute it and/or modify
  17.  # it under the terms of the GNU General Public License as published by
  18.  # the Free Software Foundation; either version 2 of the License, or
  19.  # (at your option) any later version.
  20.  # 
  21.  # This program is distributed in the hope that it will be useful,
  22.  # but WITHOUT ANY WARRANTY; without even the implied warranty of
  23.  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  24.  # GNU General Public License for more details.
  25.  # 
  26.  # You should have received a copy of the GNU General Public License
  27.  # along with this program; if not, write to the Free Software
  28.  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  29.  # 
  30.  # ###################################################################
  31.  ##
  32.  
  33. #===============================================================================
  34. # This file contains proc for handling custom HTML elements.
  35. #===============================================================================
  36.  
  37.  
  38. set html::AttributeTypesDefs {
  39.     flag url color frametarget choices length integer other contenttype 
  40.     contenttypes eventhandler linktypes multilength multilengths
  41.     languagecode charset charsets coords datetime
  42.     character mediadesc
  43. }
  44. set html::AttributeTypesShow {
  45.     Flag URL Color "Frame target" Choices Length Integer Other "Content type"
  46.     "Content types" "Event handler" "Link types" "Multi length" "Multi lengths"
  47.     "Language code" "Character set" "Character sets" Lengths "Date and time"
  48.     Character "Media descriptors"
  49. }
  50.  
  51. proc html::NewElement {} {
  52.     global html::ElemAttrOptional html::TmpFolder
  53.     set invalidInput 1
  54.     set values {"" 1 1 0 0}
  55.     while {$invalidInput} {
  56.         set box "-t {New element} 10 10 100 25 -e [list [lindex $values 0]] 110 10 250 25 \
  57.         -c {Has closing tag} [lindex $values 1] 10 40 150 55 \
  58.         -t {Element type} 10 80 100 95 -r Normal [lindex $values 2] 10 100 100 115 \
  59.         -r {INPUT element with TYPE given above} [lindex $values 3] 10 120 300 135 \
  60.         -r {Plug-in using EMBED} [lindex $values 4] 10 140 200 155 \
  61.         -b OK 20 170 85 190 -b Cancel 105 170 170 190"
  62.         set values [eval [concat dialog -w 340 -h 200 $box]]
  63.         if {[lindex $values 6]} {return}
  64.         set element [string toupper [string trim [lindex $values 0]]]
  65.         set closingTag [lindex $values 1]
  66.         if {[lindex $values 2]} {
  67.             set elemType normal
  68.         } elseif {[lindex $values 3]} {
  69.             set elemType input
  70.         } else {
  71.             set elemType plugin
  72.         }
  73.         # Check that input is ok.
  74.         if {$element == ""} {
  75.             alertnote "You must specify the element."
  76.         } elseif {[info exists html::ElemAttrOptional($element)]} {
  77.             alertnote "The element $element is already defined."
  78.         } elseif {![regexp {^[a-zA-Z_][-_.a-zA-Z0-9]*$} $element]} {
  79.             alertnote "Invalid characters in element name. For example, it may not contain spaces."
  80.         } else {
  81.             set invalidInput 0
  82.         }
  83.     }
  84.  
  85.     if {$elemType == "input"} {set element "INPUT TYPE=$element"}
  86.     # Check if there is already a window.
  87.     if {![catch {bringToFront "* Defining element $element *"}]} {return}
  88.     # Get a key binding.
  89.     if {[catch {dialog::getAKey $element ""} keyStr]} {return}
  90.     # Get the layout.
  91.     if {!$closingTag} {
  92.         set layout [html::SetLayoutEmpty {0 0} $element]
  93.     } elseif {$elemType == "normal"} {
  94.         set layout [html::SetLayoutClosing {1 0 0 0} $element]
  95.     } else {
  96.         # dummy for INPUT and plugins.
  97.         set layout open00
  98.     }
  99.     
  100.     file::ensureDirExists ${html::TmpFolder}
  101.     set fid [open [file join ${html::TmpFolder} "NE $element"] w]
  102.     puts $fid "\n$layout\nCustom\n$elemType\n$keyStr\nvisible"
  103.     if {$elemType == "plugin"} {
  104.         set out ""
  105.         set req [html::GetRequired EMBED]
  106.         set attrs [concat $req [html::GetOptional EMBED 1]]
  107.         for {set i 0} {$i < [llength $attrs]} {incr i} {
  108.             set a [lindex $attrs $i]
  109.             append out $a " " [set t [html::GetAttrType EMBED $a]] " " [expr {$i < [llength $req]}]
  110.             if {$t == "length" || $t == "integer" || $t == "multilength" || $t == "multilengths" || $t == "coords"} {
  111.                 append out " " [html::GetAttrRange EMBED $a]
  112.             }
  113.             if {$t == "choices"} {
  114.                 append out " " [html::GetAttrChoices EMBED $a]
  115.             }
  116.             append out "\n"
  117.         }
  118.         puts -nonewline $fid $out
  119.     }
  120.     close $fid
  121.     
  122.     # Get the attributes    
  123.     html::ChangeAddition $element
  124.  
  125. }
  126.  
  127. proc html::EditElement {} {
  128.     global html::ElemAttrOptional html::PrefsFolder html::TmpFolder
  129.     if {[catch {listpick -p "Select element to edit." \
  130.       [lsort [array names html::ElemAttrOptional]]} element] || $element == ""} {return}
  131.     if {![file exists [file join ${html::TmpFolder} "NE $element"]] && [file exists [file join ${html::PrefsFolder} "New elements" $element]]} {
  132.         file copy [file join ${html::PrefsFolder} "New elements" $element] \
  133.           [file join ${html::TmpFolder} "NE $element"]
  134.     }
  135.     html::ChangeAddition $element
  136. }
  137.  
  138. proc html::ChangeAddition {elem} {
  139.     global html::PrefsFolder screenHeight html::AttributeTypesDefs html::_preDefinedRequired
  140.     global html::AttributeTypesShow html::_tmpRequired html::_tmpOptional html::_tmpChoices
  141.     global html::_tmpRange html::_tmpType html::TmpFolder html::_preDefinedOptional
  142.     global html::_tmpExtraChoices
  143.     
  144.     # Check if there is already a window.
  145.     if {![catch {bringToFront "* Defining element $elem *"}]} {return}
  146.     set html::_tmpOptional($elem) ""
  147.     set html::_tmpRequired($elem) ""
  148.     foreach var {html::_tmpExtraChoices html::_tmpRange html::_tmpChoices html::_tmpType} {
  149.         foreach arr [array names $var] {
  150.             if {[string match "$elem%*" $arr]} {unset [set var]($arr)}
  151.         }
  152.     }
  153.     new -n "* Defining element $elem *" -m HTMx -g 400 40 270 [expr {$screenHeight - 60}] -shell 1
  154.     set isNew [file exists [file join ${html::TmpFolder} "NE $elem"]]
  155.     set txt "N = new attribute\ndelete or backspace = delete attribute\nreturn or enter = edit attribute\n"
  156.     append txt "Close window to save.\nPress shift key while closing to cancel.\n\n"
  157.     if {$isNew} {
  158.         set fid [open [file join ${html::TmpFolder} "NE $elem"] r]
  159.         set content [read -nonewline $fid]
  160.         close $fid
  161.         foreach l [lrange [split $content "\n"] 6 end] {
  162.             append txt [eval html::AdditionText $l]
  163.         }
  164.         insertText $txt
  165.     } else {
  166.         if {[file exists [file join ${html::PrefsFolder} "Modified elements" $elem]]} {
  167.             set fid [open [file join ${html::PrefsFolder} "Modified elements" $elem] r]
  168.             set content [read -nonewline $fid]
  169.             close $fid
  170.             foreach l [lrange [split $content "\n"] 1 end] {
  171.                 if {[string index $l 0] == "#"} {
  172.                     set html::_tmpExtraChoices($elem%[string trim [lindex $l 0] #]) [lrange $l 1 end]
  173.                 } else {
  174.                     append txt [eval html::AdditionText $l]
  175.                 }
  176.             }
  177.         }
  178.         set html::_preDefinedRequired($elem) [lremove -l [html::GetRequired $elem] [set html::_tmpRequired($elem)]]
  179.         set html::_preDefinedOptional($elem) [lremove -l [html::GetOptional $elem 1] [set html::_tmpOptional($elem)]]
  180.         set attrs [concat [set html::_preDefinedRequired($elem)] [set html::_preDefinedOptional($elem)]]
  181.         for {set i 0} {$i < [llength $attrs]} {incr i} {
  182.             set a [lindex $attrs $i]
  183.             set type [html::GetAttrType $elem $a]
  184.             if {$type == "length" || $type == "integer" || $type == "multilength" || $type == "multilengths" || $type == "coords"} {
  185.                 append txt "•" [html::AdditionText $a $type [expr {$i < [llength [set html::_preDefinedRequired($elem)]]}] [html::GetAttrRange $elem $a]]
  186.             } elseif {$type == "choices"} {
  187.                 set ch ""
  188.                 foreach c [html::GetAttrChoices $elem $a] {
  189.                     if {[lcontains html::_tmpExtraChoices($elem%$a) $c]} {
  190.                         lappend ch $c
  191.                     } else {
  192.                         lappend ch "•$c"
  193.                     }
  194.                 }
  195.                 append txt "•" [eval html::AdditionText $a $type [expr {$i < [llength [set html::_preDefinedRequired($elem)]]}] $ch]
  196.             } else {
  197.                 append txt "•" [html::AdditionText $a $type [expr {$i < [llength [set html::_preDefinedRequired($elem)]]}]]
  198.             }
  199.         }
  200.         insertText $txt
  201.     }
  202.     beginningOfBuffer
  203.     select [html::customTopLine] [nextLineStart [html::customTopLine]]
  204.     insertColorEscape [minPos] 1
  205.     insertColorEscape [prevLineStart [html::customTopLine]] 0
  206.     refresh
  207.     setWinInfo read-only 1
  208. }
  209.  
  210. proc html::AdditionText {args} {
  211.     global html::AttributeTypesDefs
  212.     global html::AttributeTypesShow html::_tmpRequired html::_tmpOptional html::_tmpChoices
  213.     global html::_tmpRange html::_tmpType
  214.  
  215.     set elem [html::FindElemInWindow]
  216.     set optreq {"" " required"}
  217.     set showtype [lindex ${html::AttributeTypesShow} [lsearch -exact ${html::AttributeTypesDefs} [set type [lindex $args 1]]]]
  218.     if {$showtype == ""} {set showtype $type}
  219.     append txt [string trim [lindex $args 0] =] " " $showtype [lindex $optreq [lindex $args 2]] "\n"
  220.     if {[lindex $args 2]} {
  221.         lappend html::_tmpRequired($elem) [lindex $args 0]
  222.     } else {
  223.         lappend html::_tmpOptional($elem) [lindex $args 0]
  224.     }
  225.     set html::_tmpType($elem%[lindex $args 0]) [lindex $args 1]
  226.     if {$type == "length" || $type == "integer" || $type == "multilength" || $type == "multilengths" || $type == "coords"} {
  227.         regexp {([-i0-9]+):([-i0-9]+)} [lindex $args 3] "" min max
  228.         append txt "    Minimum value: "
  229.         if {$min == "-i"} {
  230.             append txt "Not specified\n"
  231.         } else {
  232.             append txt "$min\n"
  233.         }
  234.         append txt "    Maximum value: "
  235.         if {$max == "i"} {
  236.             append txt "Not specified\n"
  237.         } else {
  238.             append txt "$max\n"
  239.         }
  240.         set html::_tmpRange($elem%[lindex $args 0]) [lindex $args 3]
  241.     }
  242.     if {$type == "choices"} {
  243.         foreach c [split [lrange $args 3 end]] {
  244.             append txt "    $c\n"
  245.             lappend html::_tmpChoices($elem%[lindex $args 0]) $c
  246.         }
  247.     }
  248.     return $txt
  249. }
  250.  
  251. proc html::NewAttribute {{thisattr ""} {pos ""}} {
  252.     global html::AttributeTypesShow html::_tmpRequired html::_tmpOptional
  253.     global html::AttributeTypesDefs html::_tmpType html::_tmpRange html::_tmpChoices
  254.     
  255.     set elem [html::FindElemInWindow]
  256.     set defattr $thisattr
  257.     if {$thisattr == ""} {
  258.         set values {0 0 {} Other 0}
  259.     } else {
  260.         set deftype [set html::_tmpType($elem%$thisattr)]
  261.         set values [list 0 0 [string trim $thisattr =] \
  262.           [lindex ${html::AttributeTypesShow} [lsearch -exact  ${html::AttributeTypesDefs} [set html::_tmpType($elem%$thisattr)]]] \
  263.           [lcontains html::_tmpRequired($elem) $thisattr]]
  264.     }
  265.     set invalidInput 1
  266.     while {$invalidInput} {
  267.         set box "-t {Attribute for $elem} 10 10 330 25 \
  268.           -e [list [lindex $values 2]] 10 40 150 55 \
  269.           -t Type: 170 40 205 55 \
  270.           -m [list [concat [list [lindex $values 3]] ${html::AttributeTypesShow}]] \
  271.           210 40 330 60 -c Required [lindex $values 4] 10 70 130 85"
  272.         set values [eval [concat dialog -w 490 -h 90 \
  273.           -b OK 340 10 405 30 -b Cancel 340 40 405 60 $box]]
  274.         if {[lindex $values 1]} {
  275.             error "Cancel"
  276.         } elseif {[lindex $values 0]} {
  277.             set thisattr [string trim [lindex $values 2]]
  278.             set thistype [lindex $values 3]
  279.             if {$thistype != "Event handler"} {set thisattr [string toupper $thisattr]}
  280.             if {$thistype != "Flag"} {append thisattr =}
  281.             set required [lindex $values 4]
  282.             if {$thisattr == ""} {
  283.                 alertnote "You must specify the attribute name."
  284.             } elseif {![regexp {^[a-zA-Z_][-_.a-zA-Z0-9]*=?$} $thisattr]} {
  285.                 alertnote "Invalid characters in attribute. For example, it may not contain spaces."
  286.             } elseif {$thisattr != $defattr && [lsearch -exact [concat [set html::_tmpOptional($elem)] [set html::_tmpRequired($elem)]] $thisattr] >= 0} {
  287.                 alertnote "$elem already has an attribute [string trim $thisattr =]."
  288.             } else {
  289.                 set invalidInput 0
  290.                 set thistype [lindex ${html::AttributeTypesDefs} [lsearch -exact ${html::AttributeTypesShow} $thistype]]
  291.             }
  292.         }
  293.     }
  294.     
  295.     set rangechoices ""
  296.     if {$thistype == "length" || $thistype == "integer" || $thistype == "multilength" || $thistype == "multilengths" || $thistype == "coords"} {
  297.         if {$defattr != "" && $thistype == $deftype} {
  298.             regexp {([-i0-9]+):([-i0-9]+)} [set html::_tmpRange($elem%$defattr)] "" min max
  299.             if {![regexp {^[0-9]+$} $min]} {set min ""}
  300.             if {![regexp {^[0-9]+$} $max]} {set max ""}
  301.             set rangechoices [html::NewRange $thisattr $min $max]
  302.         } else {
  303.             set rangechoices [html::NewRange $thisattr]
  304.         }
  305.     }
  306.     
  307.     if {$thistype == "choices"} {
  308.         if {$defattr != "" && $thistype == $deftype} {
  309.             set rangechoices [html::NewChoices $thisattr [set html::_tmpChoices($elem%$defattr)]]
  310.         } else {
  311.             set rangechoices [html::NewChoices $thisattr]
  312.         }
  313.     }
  314.     
  315.     if {$pos != ""} {
  316.         html::DeleteAttributes $pos
  317.     }
  318.     set txt [eval html::AdditionText $thisattr $thistype $required $rangechoices]
  319.     if {$pos != ""} {
  320.         goto $pos
  321.     } elseif {![catch {search -s "•" 0} res]} {
  322.         goto [lindex $res 0]
  323.     } else {
  324.         goto [maxPos]
  325.     }
  326.     setWinInfo read-only 0
  327.     insertText $txt
  328.     setWinInfo read-only 1
  329. }
  330.  
  331. proc html::NewRange {attr {min ""} {max ""}} {
  332.     set values [list 0 0 $min $max]
  333.     while {1} {
  334.         set box "-t {Range for [string trim $attr =]} 60 10 290 25 -t {Minvalue:} 10 40 100 55 \
  335.           -e [list [lindex $values 2]] 110 40 130 55 -t {Maxvalue:} 150 40 240 55 \
  336.           -e [list [lindex $values 3]] 250 40 270 55"
  337.         set values [eval [concat dialog -w 300 -h 120 \
  338.           -b OK 20 90 85 110 -b Cancel 105 90 170 110 $box]]
  339.         set min [string trim [lindex $values 2]]
  340.         set max [string trim [lindex $values 3]]
  341.         if {[lindex $values 1]} {
  342.             error "Cancel"
  343.         } elseif {[lindex $values 0]} {
  344.             if {$min != "" && ![html::IsInteger $min]} {
  345.                 alertnote "Not a valid number for minimum value."
  346.             } elseif {$max != "" && ![html::IsInteger $max]} {
  347.                 alertnote "Not a valid number for maximum value."
  348.             } elseif {$min != "" && $max != "" && $max < $min} {
  349.                 alertnote "Maxvalue is smaller than minvalue."
  350.             } else {
  351.                 break
  352.             }
  353.         }
  354.     }
  355.     if {$min == ""} {
  356.         set range "-i:"
  357.     } else {
  358.         set range "$min:"
  359.     }
  360.     if {$max != ""} {
  361.         append range "$max"
  362.     } else {
  363.         append range "i"
  364.     }
  365.     return $range
  366. }
  367.  
  368. proc html::NewChoices {attr {choices ""}} {
  369.     set i 0
  370.     set done 0
  371.     while {!$done} {
  372.         incr i
  373.         set values {0 0 {}}
  374.         set invalidInput 1
  375.         while {$invalidInput} {
  376.             set box "-t {Choice $i for $attr} 10 10 210 25 \
  377.             -e [list [lindex $values 2]] 10 40 200 55"
  378.             if {$i > 1 || [llength $choices]} {append box " -b {No more choices} 220 70 340 90"}
  379.             if {$i > 1} {append box " -b {Remove last} 220 100 340 120"}
  380.             set wi 10
  381.             set ht 90
  382.             if {[llength $choices]} {
  383.                 append box " -t {All choices} 10 70 200 85"
  384.                 foreach ch $choices {
  385.                     append box " -t [string trim $ch •] $wi $ht [expr {$wi + 95}] [expr {$ht + 15}]"
  386.                     incr wi 100
  387.                     if {$wi == 210} {
  388.                         set wi 10
  389.                         incr ht 20
  390.                     }
  391.                 }
  392.             }
  393.             if {$wi == 110} {incr ht 20}
  394.             if {$ht < 130} {set ht 130}
  395.             set values [eval [concat dialog -w 350 -h $ht \
  396.             -b OK 220 10 285 30 -b Cancel 220 40 285 60 \
  397.             $box]]
  398.             if {[lindex $values 1]} {
  399.                 error "Cancel"
  400.             } elseif {($i > 1 || [llength $choices]) && [lindex $values 3] } {
  401.                 set done 1
  402.                 break
  403.             } elseif {$i > 1 && [lindex $values 4]} {
  404.                 incr i -1
  405.                 set choices [lrange $choices 0 [expr {[llength $choices] - 2}]]
  406.             } elseif {[lindex $values 0]} {
  407.                 set thischoice [string toupper [string trim [lindex $values 2]]]
  408.                 if {$thischoice != "" && ![regexp {^[a-zA-Z_][-_.a-zA-Z0-9]*=?$} $thischoice]} {
  409.                     alertnote "Invalid characters in choice.  For example, it may not contain spaces."
  410.                 } elseif {$thischoice != ""} {
  411.                     if {[lcontains choices $thischoice]} {
  412.                         alertnote "$attr already has a choice $thischoice."
  413.                     } else {
  414.                         set invalidInput 0
  415.                     }
  416.                 }
  417.             }
  418.         }
  419.         if {!$done} {lappend choices $thischoice}
  420.     }
  421.     return $choices
  422. }
  423.  
  424. proc html::HTMxStartPos {s e} {
  425.     upvar $s spos $e epos
  426.     set start [rowColToPos 5 0]
  427.     set spos [lineStart [getPos]]
  428.     if {[pos::compare $spos < $start]} {set spos $start}
  429.     set epos [selEnd]
  430.     if {[pos::compare $epos < $spos]} {error "Incorrect pos"}
  431.     if {[lookAt [pos::math $epos - 1]] != "\r"} {set epos [nextLineStart $epos]}
  432. }
  433.  
  434. proc html::EditAttribute {} {
  435.     global html::_tmpChoices html::_tmpExtraChoices
  436.     set elem [html::FindElemInWindow]
  437.     html::HTMxStartPos spos epos
  438.     while {[lookAt $spos] == " "} {set spos [prevLineStart $spos]}
  439.     set txt [getText $spos [nextLineStart $spos]]
  440.     set attr [lindex $txt 0]
  441.     if {[set type [lindex $txt 1]] != "Flag"} {append attr "="}
  442.     if {[lookAt $spos] == "•"} {
  443.         if {$type != "Choices"} {return}
  444.         set ch [html::NewChoices [set attr [string trim $attr "•"]] [set html::_tmpChoices($elem%$attr)]]
  445.         if {[set newchoices [lrange $ch [llength [set html::_tmpChoices($elem%$attr)]] end]] == ""} {return}
  446.         append html::_tmpChoices($elem%$attr) " " $newchoices
  447.         append html::_tmpExtraChoices($elem%$attr) " " $newchoices
  448.         set spos [nextLineStart $spos]
  449.         while {[lookAt $spos] == " "} {set spos [nextLineStart $spos]}
  450.         goto $spos
  451.         set txt ""
  452.         foreach c $newchoices {
  453.             append txt "    $c\n"
  454.         }
  455.         setWinInfo read-only 0
  456.         insertText $txt
  457.         setWinInfo read-only 1
  458.     } else {
  459.         html::NewAttribute $attr $spos
  460.     }
  461. }
  462.  
  463. proc html::DeleteAttributes {{spos ""}} {
  464.     global html::_tmpOptional html::_tmpRequired html::_tmpRange
  465.     global html::_tmpType html::_tmpChoices html::_tmpExtraChoices
  466.     
  467.     set elem [html::FindElemInWindow]
  468.     if {$spos == ""} {
  469.         html::HTMxStartPos spos epos
  470.     } else {
  471.         set epos [nextLineStart $spos]
  472.     }
  473.     while {[lookAt $spos] == " " && [regexp {:} [getText $spos [nextLineStart $spos]]]} {set spos [prevLineStart $spos]}
  474.     set s0pos $spos
  475.     set delchoices 0
  476.     while {[lookAt $spos] == " " && [pos::compare $spos < $epos]} {
  477.         set delchoices 1
  478.         set extra 0
  479.         set s1 $spos
  480.         while {[lookAt $s1] == " "} {set s1 [prevLineStart $s1]}
  481.         set attr [lindex [getText $s1 [nextLineStart $s1]] 0]
  482.         if {[string index $attr 0] == "•"} {
  483.             set extra 1
  484.             set attr [string trim $attr •]
  485.         }
  486.         set choice [lindex [getText $spos [nextLineStart $spos]] 0]
  487.         if {[string index $choice 0] != "•"} {
  488.             set html::_tmpChoices($elem%$attr=) [lremove -l [set html::_tmpChoices($elem%$attr=)] $choice]
  489.             if {$extra} {set html::_tmpExtraChoices($elem%$attr=) [lremove -l [set html::_tmpExtraChoices($elem%$attr=)] $choice]}
  490.         } else {
  491.             set s0pos [nextLineStart $spos]
  492.         }
  493.         set spos [nextLineStart $spos]
  494.     }
  495.     if {$delchoices && ![llength [set html::_tmpChoices($elem%$attr=)]]} {
  496.         set spos [prevLineStart $spos]
  497.         while {[lookAt $spos] == " "} {set spos [prevLineStart $spos]}
  498.         set s0pos $spos
  499.     }
  500.     while {[lookAt $spos] != "•" && [pos::compare $spos < $epos]} {
  501.         set txt [getText $spos [nextLineStart $spos]]
  502.         set attr [lindex $txt 0]
  503.         if {[set type [lindex $txt 1]] != "Flag"} {append attr "="}
  504.         set html::_tmpRequired($elem) [lremove -l [set html::_tmpRequired($elem)] $attr]
  505.         set html::_tmpOptional($elem) [lremove -l [set html::_tmpOptional($elem)] $attr]
  506.         catch {unset html::_tmpRange($elem%$attr)}
  507.         catch {unset html::_tmpChoices($elem%$attr)}
  508.         catch {unset html::_tmpType($elem%$attr)}
  509.         set spos [nextLineStart $spos]
  510.         while {[lookAt $spos] == " "} {set spos [nextLineStart $spos]}
  511.     }
  512.     setWinInfo read-only 0
  513.     deleteText $s0pos $spos
  514.     setWinInfo read-only 1
  515. }
  516.  
  517. proc html::HTMxCloseHook {name} {
  518.     global html::_tmpRequired html::_tmpOptional html::_tmpChoices HTMLmodeVars CSSmodeVars
  519.     global html::_tmpRange html::_tmpType html::PrefsFolder htmlMenuKey htmlVersion
  520.     global html::ElemAttrRequired html::ElemAttrOptional html::AttrChoices html::AttrRange
  521.     global html::ElemLayout html::AttrType html::TmpFolder html::_preDefinedRequired
  522.     global html::_preDefinedOptional html::_tmpExtraChoices html::Plugins cssModeIsLoaded
  523.     
  524.     regexp {\* Defining element (.+) +\*$} $name "" elem
  525.     set isNew [file exists [file join ${html::TmpFolder} "NE $elem"]]
  526.     if {[key::shiftPressed]} {
  527.         if {$isNew} {file delete [file join ${html::TmpFolder} "NE $elem"]}
  528.         return
  529.     }
  530.     if {$isNew} {
  531.         set fid [open [file join ${html::TmpFolder} "NE $elem"] r]
  532.         gets $fid
  533.         set out "$htmlVersion\n[set layout [gets $fid]]\n[set custmenu [gets $fid]]\n[set elemType [gets $fid]]\n[set key [gets $fid]]\n[set visibility [gets $fid]]\n"
  534.         close $fid
  535.         file delete [file join ${html::TmpFolder} "NE $elem"]
  536.         set html::ElemAttrRequired($elem) ""
  537.         set html::ElemAttrOptional($elem) ""
  538.         ensureset html::ElemLayout($elem) $layout
  539.         if {$elemType == "plugin"} {
  540.             set html::Plugins [lunique [concat ${html::Plugins} $elem]]
  541.         }
  542.         html::ReadMenuKeys
  543.         set melem $elem
  544.         regexp "INPUT TYPE=(.*)" $elem "" melem
  545.         set defCSSkey 0
  546.         if {![info exists htmlMenuKey(Custom/[set me [string index $melem 0][string tolower [string range $melem 1 end]]])]} {
  547.             set htmlMenuKey(Custom/$me) $key
  548.             set defCSSkey 1
  549.         }
  550.         html::WriteMenuKeys
  551.     } else {
  552.         set out "$htmlVersion\n"
  553.         set html::ElemAttrRequired($elem) [set html::_preDefinedRequired($elem)]
  554.         set html::ElemAttrOptional($elem) [set html::_preDefinedOptional($elem)]
  555.     }
  556.     
  557.     set attrs [concat [set html::_tmpRequired($elem)] [set html::_tmpOptional($elem)]]
  558.     set numreq [llength [set html::_tmpRequired($elem)]]
  559.     if {!$isNew} {
  560.         set attrs [lremove -l $attrs [concat [set html::_preDefinedRequired($elem)] [set html::_preDefinedOptional($elem)]]]
  561.         incr numreq [expr {-[llength [set html::_preDefinedRequired($elem)]]}]
  562.     }
  563.     
  564.     for {set i 0} {$i < [llength $attrs]} {incr i} {
  565.         set a [lindex $attrs $i]
  566.         append out $a " " [set t [set html::_tmpType($elem%$a)]] " " [expr {$i < $numreq}]
  567.         set html::AttrType($elem%$a) $t
  568.         if {$t == "eventhandler"} {set html::AttrType($elem%[string toupper $a]) $t}
  569.         if {$i < $numreq} {
  570.             lappend html::ElemAttrRequired($elem) $a
  571.         } else {
  572.             lappend html::ElemAttrOptional($elem) $a
  573.         }
  574.         if {$t == "length" || $t == "integer" || $t == "multilength" || $t == "multilengths" || $t == "coords"} {
  575.             append out " " [set html::_tmpRange($elem%$a)]
  576.             set html::AttrRange($elem%$a) [set html::_tmpRange($elem%$a)]
  577.         }
  578.         if {$t == "choices"} {
  579.             append out " " [set html::_tmpChoices($elem%$a)]
  580.             set html::AttrChoices($elem%$a) [set html::_tmpChoices($elem%$a)]
  581.         }
  582.         append out "\n"
  583.     }
  584.     foreach a [array names html::_tmpExtraChoices] {
  585.         if {[string match "$elem%*" $a] && [llength [set html::_tmpExtraChoices($a)]]} {
  586.             append out "#[string range $a [expr {[string length $elem] + 1}] end] " "[set html::_tmpExtraChoices($a)]\n"
  587.             regsub -all "•" [set html::_tmpChoices($a)] "" c
  588.             set html::AttrChoices($a) $c
  589.         }
  590.     }
  591.     
  592.     if {$isNew} {
  593.         file::ensureDirExists [file join ${html::PrefsFolder} "New elements"]
  594.         set fid [open [file join ${html::PrefsFolder} "New elements" $elem] w]
  595.     } elseif {$out != "$htmlVersion\n"} {
  596.         file::ensureDirExists [file join ${html::PrefsFolder} "Modified elements"]
  597.         set fid [open [file join ${html::PrefsFolder} "Modified elements" $elem] w]
  598.     } else {
  599.         if {[file exists [file join ${html::PrefsFolder} "Modified elements" $elem]]} {
  600.             html::RemoveAdditions2 $elem
  601.         }
  602.         return
  603.     }
  604.     
  605.     puts -nonewline $fid $out
  606.     close $fid
  607.     if {$isNew} {
  608.         if {[llength [glob -dir [file join ${html::PrefsFolder} "New elements"] *]] == 1} {
  609.             menu::buildSome htmlMenu
  610.         } else {
  611.             menu::buildSome Custom
  612.         }
  613.     }
  614.     
  615.     if {!$HTMLmodeVars(simpleColoring)} {
  616.         if {$HTMLmodeVars(ColorImmediately)} {
  617.             regModeKeywords -a -k $HTMLmodeVars(tagColor) HTML $elem
  618.             regsub -all = $attrs "" attrs
  619.         } else {
  620.             regModeKeywords -a -k $HTMLmodeVars(tagColor) HTML [concat "<$elem" "/$elem"]
  621.         }
  622.         regModeKeywords -a -k $HTMLmodeVars(attributeColor) HTML $attrs
  623.     }
  624.     if {[info exists cssModeIsLoaded]} {
  625.         regModeKeywords -a -k $CSSmodeVars(htmlColor) CSS $elem
  626.     }
  627.     html::EnableExtend
  628.     html::DeleteCache "Additions cache"
  629.     html::DeleteCache "Additions coloring cache"
  630.  
  631.     if {$isNew && $defCSSkey} {
  632.         html::DeleteCache "CSS keybindings cache"
  633.         set csselem $elem
  634.         if {$elemType == "plugin"} {set csselem EMBED}
  635.         if {[regexp "INPUT TYPE=(.*)" $elem]} {set csselem INPUT}
  636.         if {[info exists cssModeIsLoaded]} {css::BindOneKey $key $csselem}
  637.     }
  638.     if {[llength [set html::_tmpOptional($elem)]]} {html::UseAttributes2 $elem}
  639. }
  640.  
  641. proc html::RemoveAdditions {} {
  642.     global html::PrefsFolder
  643.     if {![html::AdditionsExists]} {return}
  644.     foreach f [concat [glob -nocomplain -dir [file join ${html::PrefsFolder} "New elements"] *] \
  645.       [glob -nocomplain -dir [file join ${html::PrefsFolder} "Modified elements"] *]] {
  646.         lappend elems [file tail $f]
  647.     }
  648.     if {[catch {listpick -p "Select element to remove additions from." [lsort $elems]} element] || \
  649.       $element == "" || [askyesno "Remove additions from $element?"] == "no"} {
  650.         return
  651.     }
  652.     html::RemoveAdditions2 $element
  653.     message "Additions removed."
  654. }
  655.   
  656. proc html::RemoveAdditions2 {element} {  
  657.     global html::PrefsFolder html::ElemAttrOptional html::ElemAttrRequired html::AttrChoices 
  658.     global html::AttrRange html::AttrType html::ElemLayout html::ElemAttrUsed html::ElemAttrHidden
  659.     global htmlMenuKey cssModeIsLoaded html::Plugins
  660.     set isNew [file exists [file join ${html::PrefsFolder} "New elements" $element]]
  661.     if {$isNew} {
  662.         foreach a [concat [html::GetRequired $element] [html::GetOptional $element 1]] {
  663.             catch {unset html::AttrChoices($element%$a)}
  664.             catch {unset html::AttrRange($element%$a)}
  665.             catch {unset html::AttrType($element%$a)}
  666.         }
  667.         catch {unset html::ElemAttrRequired($element)}
  668.         catch {unset html::ElemAttrOptional($element)}
  669.         catch {unset html::ElemAttrUsed($element)}
  670.         catch {unset html::ElemAttrHidden($element)}
  671.         catch {unset html::ElemAttrOverride($element)}
  672.         catch {unset html::ElemLayout($element)}
  673.         prefs::removeArrayElement html::ElemAttrUsed $element
  674.         prefs::removeArrayElement html::ElemAttrHidden $element
  675.         prefs::removeArrayElement html::ElemAttrOverride $element
  676.         prefs::removeArrayElement html::ElemLayout $element
  677.         set html::Plugins [lremove ${html::Plugins} $element]
  678.         html::ReadMenuKeys
  679.         set melem $element
  680.         regexp "INPUT TYPE=(.*)" $element "" melem
  681.         if {[catch {set htmlMenuKey(Custom/[string index $melem 0][string tolower [string range $melem 1 end]])} key]} {set key ""}
  682.         catch {unset htmlMenuKey(Custom/[string index $melem 0][string tolower [string range $melem 1 end]])}
  683.         html::WriteMenuKeys
  684.         file delete [file join ${html::PrefsFolder} "New elements" $element]
  685.         if {![llength [glob -nocomplain -dir [file join ${html::PrefsFolder} "New elements"] *]]} {
  686.             menu::buildSome htmlMenu
  687.         } else {
  688.             menu::buildSome Custom
  689.         }
  690.     } else {
  691.         set fid [open [file join ${html::PrefsFolder} "Modified elements" $element] r]
  692.         set content [read -nonewline $fid]
  693.         close $fid
  694.         set reqs ""
  695.         set opts ""
  696.         foreach l [lrange [split $content "\n"] 1 end] {
  697.             set a [lindex $l 0]
  698.             if {[string index $a 0] == "#"} {
  699.                 set a [string trim $a #]
  700.                 set html::AttrChoices($element%$a) [lremove -l [set html::AttrChoices($element%$a)] [lrange $l 1 end]]
  701.                 continue
  702.             }
  703.             if {[lindex $l 2]} {
  704.                 lappend reqs $a
  705.             } else {
  706.                 lappend opts $a
  707.             }
  708.             if {[info exists html::ElemAttrUsed($element)]} {set html::ElemAttrUsed($element) [lremove [set html::ElemAttrUsed($element)] $a]}
  709.             if {[info exists html::ElemAttrHidden($element)]} {set html::ElemAttrHidden($element) [lremove [set html::ElemAttrHidden($element)] $a]}
  710.             catch {unset html::AttrChoices($element%$a)}
  711.             catch {unset html::AttrRange($element%$a)}
  712.             catch {unset html::AttrType($element%$a)}
  713.         }
  714.         if {[info exists html::ElemAttrRequired($element)]} {
  715.             set html::ElemAttrRequired($element) [lremove -l [set html::ElemAttrRequired($element)] $reqs]
  716.         }
  717.         set html::ElemAttrOptional($element) [lremove -l [set html::ElemAttrOptional($element)] $opts]
  718.         if {[info exists html::ElemAttrUsed($element)]} {prefs::modifiedArrayElement $element html::ElemAttrUsed}
  719.         if {[info exists html::ElemAttrHidden($element)]} {prefs::modifiedArrayElement $element html::ElemAttrHidden}
  720.         file delete [file join ${html::PrefsFolder} "Modified elements" $element]
  721.     }
  722.     
  723.     html::EnableExtend
  724.     html::DeleteCache "Additions cache"
  725.     html::DeleteCache "Additions coloring cache"
  726.     if {$isNew} {
  727.         html::DeleteCache "CSS keybindings cache"
  728.         if {[info exists cssModeIsLoaded]} {css::BindOneKey $key $element un}
  729.     }
  730. }
  731.  
  732. proc html::CreateAdditionCaches {} {
  733.     global html::PrefsFolder
  734.     
  735.     set files [concat [set newelems [glob -nocomplain -dir [file join ${html::PrefsFolder} "New elements"] *]] \
  736.       [glob -nocomplain -dir [file join ${html::PrefsFolder} "Modified elements"] *]]
  737.     
  738.     set txt ""
  739.     set ctxt ""
  740.     for {set i 0} {$i < [llength $files]} {incr i} {
  741.         set f [lindex $files $i]
  742.         set elem [file tail $f]
  743.         set isNew [expr {$i < [llength $newelems]}]
  744.         set fid [open $f r]
  745.         set content [split [read -nonewline $fid] "\n"]
  746.         close $fid
  747.         if {$isNew} {
  748.             append txt "set \"html::ElemAttrOptional($elem)\" {}\n"
  749.             append txt "ensureset \"html::ElemLayout($elem)\" [lindex $content 1]\n"
  750.             if {[lindex $content 3] == "plugin"} {append txt "lappend html::Plugins [list $elem]\n"}
  751.             if {[lindex $content 3] != "plugin" && [lindex $content 3] != "input"} {append ctxt "lappend allHTMLwords $elem\n"}
  752.             set content [lrange $content 5 end]
  753.         }
  754.         foreach l [lrange $content 1 end] {
  755.             set attr [lindex $l 0]
  756.             if {[string index $attr 0] == "#"} {
  757.                 set attr [string trim $attr #]
  758.                 append txt "set \"html::AttrChoices($elem%$attr)\" \{[html::GetAttrChoices $elem $attr]\}\n"
  759.                 append txt "lappend \"html::AttrChoices($elem%$attr)\" [lrange $l 1 end]\n"
  760.                 continue
  761.             }
  762.             if {[lindex $l 2]} {
  763.                 append txt "lappend \"html::ElemAttrRequired($elem)\" [lindex $l 0]\n"
  764.             } else {
  765.                 append txt "lappend \"html::ElemAttrOptional($elem)\" [lindex $l 0]\n"
  766.             }
  767.             append txt "set \"html::AttrType($elem%[lindex $l 0])\" [set t [lindex $l 1]]\n"
  768.             if {$t == "length" || $t == "integer" || $t == "multilength" || $t == "multilengths" || $t == "coords"} {
  769.                 append txt "set \"html::AttrRange($elem%[lindex $l 0])\" [lindex $l 3]\n"
  770.             }
  771.             if {$t == "choices"} {
  772.                 append txt "set \"html::AttrChoices($elem%[lindex $l 0])\" \{[lrange $l 3 end]\}\n"
  773.             }
  774.             if {$t == "eventhandler"} {
  775.                 append ctxt "lappend JavaScriptWords [lindex $l 0]\n"
  776.                 append txt "set \"html::AttrType($elem%[string toupper [lindex $l 0]])\" $t\n"
  777.             } else {
  778.                 append ctxt "lappend attributeWords [lindex $l 0]\n"
  779.             }
  780.         }
  781.     }
  782.     html::SaveCache "Additions cache" $txt
  783.     html::SaveCache "Additions coloring cache" $ctxt
  784. }
  785.  
  786. proc html::FindElemInWindow {} {
  787.     regexp {\* Defining element (.+) +\*$} [lindex [winNames] 0] "" elem
  788.     return $elem
  789. }
  790.  
  791. proc html::customBrowseUp {} {
  792.     set limit [html::customTopLine]
  793.     if {[pos::compare [getPos] > $limit]} {
  794.         set limit [pos::math [getPos] - 1]
  795.     }
  796.     select [lineStart $limit] [nextLineStart $limit]
  797. }
  798.  
  799. proc html::customBrowseDown {} {
  800.     set pos [getPos]
  801.     if {[pos::compare $pos < [html::customTopLine]]} {
  802.         set pos [prevLineStart [html::customTopLine]]
  803.     }
  804.     if {[pos::compare [nextLineStart $pos] < [maxPos]]} {
  805.         select [nextLineStart $pos] [nextLineStart [nextLineStart $pos]]
  806.     }
  807. }
  808.  
  809. proc html::customTopLine {} {
  810.     set p [minPos]
  811.     for {set i 0} {$i < 6} {incr i} {
  812.         set p [nextLineStart $p]
  813.     }
  814.     return $p
  815. }
  816.  
  817. namespace eval HTMx {}
  818.  
  819. proc HTMx::DblClick {from to} {html::EditAttribute}
  820.  
  821. Bind 'n' html::NewAttribute HTMx
  822. Bind '\r' html::EditAttribute HTMx
  823. Bind Enter html::EditAttribute HTMx
  824. Bind 0x33 html::DeleteAttributes HTMx
  825. Bind Del html::DeleteAttributes HTMx
  826. Bind down     html::customBrowseDown HTMx
  827. Bind up     html::customBrowseUp HTMx
  828. hook::register closeHook html::HTMxCloseHook HTMx
  829.